package gemini.basic.manager.impl;

import gemini.basic.dao.AddressDao;
import gemini.basic.dao.CustomerDao;
import gemini.basic.dao.DistributorDao;
import gemini.basic.dao.EventDao;
import gemini.basic.dao.LevelDao;
import gemini.basic.dao.MonthlyPointDao;
import gemini.basic.dao.PersonDao;
import gemini.basic.dto.SearchDistributorCriteria;
import gemini.basic.dto.SearchDistributorResult;
import gemini.basic.exception.UserExistedException;
import gemini.basic.manager.DistributorManager;
import gemini.basic.model.Address;
import gemini.basic.model.Customer;
import gemini.basic.model.Distributor;
import gemini.basic.model.DistributorStatus;
import gemini.basic.model.Event;
import gemini.basic.model.Level;
import gemini.basic.model.Month;
import gemini.basic.model.MonthlyPoint;
import gemini.basic.model.Person;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DistributorManagerImpl implements DistributorManager {

	private final Logger logger = LoggerFactory
			.getLogger(DistributorManager.class);

	private EventDao eventDao;

	private CustomerDao customerDao;

	private MonthlyPointDao monthlyPointDao;

	public MonthlyPointDao getMonthlyPointDao() {
		return monthlyPointDao;
	}

	public void setMonthlyPointDao(MonthlyPointDao monthlyPointDao) {
		this.monthlyPointDao = monthlyPointDao;
	}

	public CustomerDao getCustomerDao() {
		return customerDao;
	}

	public void setCustomerDao(CustomerDao customerDao) {
		this.customerDao = customerDao;
	}

	private AddressDao addressDao;

	public AddressDao getAddressDao() {
		return addressDao;
	}

	public void setAddressDao(AddressDao addressDao) {
		this.addressDao = addressDao;
	}

	public void setEventDao(EventDao eventDao) {
		this.eventDao = eventDao;
	}

	public EventDao getEventDao() {
		return eventDao;
	}

	private DistributorDao distributorDao;

	public void setDistributorDao(DistributorDao distributorDao) {
		this.distributorDao = distributorDao;
	}

	private LevelDao levelDao;

	public void setLevelDao(LevelDao levelDao) {
		this.levelDao = levelDao;
	}

	private PersonDao personDao;

	public void setPersonDao(PersonDao personDao) {
		this.personDao = personDao;
	}

	@Override
	public Distributor init(Distributor distributor) {
		logger.debug("============== update distributor");

		logger.debug("============== get from db with code");
		Distributor disDb = distributorDao.getByCode(distributor.getCode());

		logger.debug("============== get sponsor from db with code");
		Distributor disSponsorDb = distributorDao.getByCode(distributor
				.getDistributor().getCode());

		logger.debug("============== set sponsor");
		disDb.setDistributor(disSponsorDb);
		disDb.setPin(distributor.getPin());

		logger.debug("============== set status to INI");
		DistributorStatus disStatus = new DistributorStatus();
		disStatus.setCode("INI");
		disStatus.setDistributor(disDb);
		disStatus.setValidFrom(new Date());

		disDb.setDistributorStatuses(new ArrayList<DistributorStatus>(1));
		disDb.getDistributorStatuses().add(disStatus);

		logger.debug("============== update to db");
		Distributor result = distributorDao.saveOrUpdate(disDb, true);

		logger.debug("============== link with Persons");
		for (Person ps : distributor.getPersons()) {
			logger.debug("============== link with Person with Id: {}",
					ps.getId());
			ps = personDao.getById(ps.getId());
			ps.setDistributor(result);
			personDao.saveOrUpdate(ps, true);
		}

		return result;

	}

	@Override
	public Distributor getPlaceHolder() {
		logger.debug("============== get placeholder distributor");

		Distributor distributor = new Distributor();

		logger.debug("============== set default values: discount scale 25%, first level");
		distributor.setDiscountScale(Short.valueOf("25"));
		distributor.setLevel(getFirstLevel());

		logger.debug("============== set tempo values: code 1, pin 1");
		distributor.setCode("1");
		distributor.setPin("1");

		logger.debug("============== update to db");
		Distributor result = distributorDao.saveOrUpdate(distributor, true);

		logger.debug("============== create code for placeholder");
		result.setCode(createCodeFromId(result.getId()));
		return result;

	}

	private Level getFirstLevel() {
		Level result = levelDao.getAll().get(0);
		return result;
	}

	private String createCodeFromId(int id) {
		String inStr = String.valueOf(id);

		StringBuffer sb = new StringBuffer();
		sb.append("VN");

		for (int i = 0; i < 8 - inStr.length(); i++) {
			sb.append("0");
		}

		sb.append(inStr);

		String result = sb.toString();
		return result;
	}

	@Override
	public Distributor getByCode(String code) {
		Distributor result = distributorDao.getByCode(code);
		return result;
	}

	@Override
	public Distributor fetchDownlineByCode(String code) {
		Distributor distributor = distributorDao.getByCode(code);
		return distributor;
	}

	@Override
	public Distributor active(String code) {
		Distributor disDb = distributorDao.getByCode(code);

		logger.debug("============== update valid to date of last status");
		for (DistributorStatus status : disDb.getDistributorStatuses()) {
			if (status.getValidTo() == null) {
				status.setValidTo(new Date());
				break;
			}
		}

		logger.debug("============== add new status ACT");
		DistributorStatus disStatus = new DistributorStatus();
		disStatus.setCode("ACT");
		disStatus.setDistributor(disDb);
		disStatus.setValidFrom(new Date());

		disDb.getDistributorStatuses().add(disStatus);

		logger.debug("============== update to db");
		Distributor result = distributorDao.saveOrUpdate(disDb, true);
		return result;
	}

	@Override
	public Distributor getDistributorByCodePin(String code, String pin)
			throws UserExistedException {
		Distributor result = null;
		Distributor disDb = distributorDao.getByCode(code);

		if (disDb != null && disDb.getPin().equals(pin)) {
			boolean existedUser = false;
			for (Person ps : disDb.getPersons()) {
				if (ps.getUsers() != null && !ps.getUsers().isEmpty()) {
					existedUser = true;
					break;
				}
			}

			if (existedUser) {
				throw new UserExistedException();
			}

			result = disDb;
		}
		return result;
	}

	@Override
	public List<SearchDistributorResult> findDistributor(
			SearchDistributorCriteria criteria) {
		List<SearchDistributorResult> result = distributorDao
				.findDistributor(criteria);
		return result;
	}

	@Override
	public Distributor getSponsorByCode(String code) {
		Distributor currentDis = distributorDao.getByCode(code);
		logger.info("======================Sponsor Code <{}>", currentDis
				.getDistributor().getCode());
		Distributor sponsor = null;
		if (currentDis != null) {
			sponsor = distributorDao.getByCode(currentDis.getDistributor()
					.getCode());
		}
		return sponsor;
	}

	@Override
	public int updateLevel() {
		List<Distributor> distributors = distributorDao.getAllDistributors();

		int numberDistributors = distributors == null ? 0 : distributors.size();

		logger.info("=======================Number of distributors: {}",
				numberDistributors);

		if (numberDistributors > 0) {
			for (Distributor distributor : distributors) {
				logger.debug(
						"=======================Qualifying distributor <{}> ... =======================",
						distributor.getId());

				int currentLevel = distributor.getLevel().getId();

				int nextLevel = 0;
				if (getVolumePoint(distributor) >= 2000 && currentLevel < 4) {
					nextLevel = 4;
				} else if (getVolumePoint(distributor) >= 1000
						&& currentLevel < 3) {
					nextLevel = 3;
				} else if (getVolumePoint(distributor) >= 500
						&& currentLevel < 2) {
					nextLevel = 2;
				}

				if (nextLevel > currentLevel) {
					logger.info(
							"=======================Qualify from level <{}> to <{}>",
							currentLevel, nextLevel);

					Level level = levelDao.getLevelById(nextLevel);
					distributor.setLevel(level);
					distributorDao.saveOrUpdate(distributor, true);
				}

				logger.debug(
						"=======================Qualifying distributor <{}> ... finished! =======================",
						distributor.getId());
			}

		}

		return numberDistributors;
	}

	@SuppressWarnings("deprecation")
	private int getVolumePoint(Distributor dis) {
		int monthlyPoint = 0;
		Date date = new Date();
		if (dis.getMonthlyPoints() != null) {
			int currentMonth = date.getMonth() + 1;
			int currentYear = date.getYear() + 1900;

			for (MonthlyPoint monthlyP : dis.getMonthlyPoints()) {
				if (monthlyP.getMonth().getMonth() == currentMonth
						&& monthlyP.getMonth().getYear() == currentYear) {
					monthlyPoint = monthlyP.getPersonalPoint();
					break;
				}
			}
		}

		return monthlyPoint;
	}

	@Override
	public List<Level> getAllLevels() {
		List<Level> result = levelDao.getAll();
		return result;
	}

	@Override
	public void deleteLevel(Integer id) {
		levelDao.delete(levelDao.getLevelById(id));
	}

	@Override
	public Level saveOrUpdateLevel(Level level) {
		Level prevLevel = null;
		Level nextLevel = null;

		if (level.isPersisted()) {
			Level dbLevel = levelDao.getLevelById(level.getId());
			dbLevel.setDescription(level.getDescription());
			dbLevel.setDiscountScale(level.getDiscountScale());
			dbLevel.setName(level.getName());

			if ((level.getLevel() == null && dbLevel.getLevel() != null)
					|| (level.getLevel() != null && dbLevel.getLevel() == null)
					|| (level.getLevel() != null && dbLevel.getLevel() != null && !level
							.getLevel().getId()
							.equals(dbLevel.getLevel().getId()))) {

				nextLevel = dbLevel.getLevel();

				if (dbLevel.getLevels() != null
						&& !dbLevel.getLevels().isEmpty()) {
					prevLevel = dbLevel.getLevels().get(0);
				}

				if (prevLevel != null) {
					prevLevel.setLevel(nextLevel);
					levelDao.saveOrUpdate(prevLevel, true);
				}

				dbLevel.setLevel(level.getLevel());
			}

			level = dbLevel;
		}

		nextLevel = level.getLevel();
		prevLevel = null;

		if (nextLevel != null && nextLevel.getLevels() != null
				&& !nextLevel.getLevels().isEmpty()) {
			prevLevel = nextLevel.getLevels().get(0);
		}

		if (prevLevel != null) {
			prevLevel.setLevel(null);
			prevLevel = levelDao.saveOrUpdate(prevLevel, true);
		}

		if (nextLevel != null) {
			nextLevel = levelDao.getLevelById(nextLevel.getId());
		}

		level.setLevel(nextLevel);
		level = levelDao.saveOrUpdate(level, true);

		if (prevLevel != null) {
			prevLevel.setLevel(level);
			levelDao.saveOrUpdate(prevLevel, true);
		}

		return levelDao.getLevelById(level.getId());
	}

	@Override
	public List<Event> getEventByDistributorCode(String code) {
		List<Event> result = eventDao.getEventByDistributorCode(code);
		return result;
	}

	@Override
	public Event saveOrUpdateEvent(Event event) {
		
		if(event.isPersisted()){
			Address dbAddress = addressDao.getById(event.getAddress().getId());
			
			dbAddress.setAddressLine1(event.getAddress().getAddressLine1());
			dbAddress.setAddressLine2(event.getAddress().getAddressLine2());
			dbAddress.setCity(event.getAddress().getCity());
			dbAddress.setCountry(event.getAddress().getCountry());
			dbAddress.setDistrict(event.getAddress().getDistrict());
			
			dbAddress = addressDao.saveOrUpdate(dbAddress, true);
			
			Event dbEvent = eventDao.getById(event.getId());
			
			dbEvent.setDate(event.getDate());
			dbEvent.setDescription(event.getDescription());
			dbEvent.setAddress(dbAddress);
			
			event = dbEvent;
		} else {
			Address address = addressDao.saveOrUpdate(event.getAddress(), true);
			event.setAddress(address);
		}
		
		Distributor dis = distributorDao.getByCode(event.getDistributor()
				.getCode());
		event.setDistributor(dis);

		Event result = eventDao.saveOrUpdate(event, true);
		return result;
	}

	@Override
	public void deleteEvent(int id) {
		logger.debug("============== delete event");
		eventDao.deleteEvent(id);
	}

	@Override
	public List<Customer> getCustomerByDistributorCode(String code) {
		List<Customer> result = customerDao.getCustomerByDistributorCode(code);
		return result;
	}

	@Override
	public Customer saveOrUpdateCustomer(Customer customer) {
		Distributor dis = distributorDao.getByCode(customer.getDistributor()
				.getCode());
		customer.setDistributor(dis);

		List<Person> persons = null;

		if (customer.getPersons() != null || !customer.getPersons().isEmpty()) {
			
			persons = new ArrayList<Person>(customer.getPersons().size());
			
			Address address = null;
			if (customer.getPersons().get(0).getAddress() != null) {
				address = addressDao.saveOrUpdate(customer.getPersons().get(0)
						.getAddress(), true);
			}
			Person person = customer.getPersons().get(0);
			person.setAddress(address);
			persons.add(person);
		}

		customer = customerDao.saveOrUpdate(customer, true);
		
		if(persons != null){
			for (Person person : persons) {
				person.setCustomer(customer);
				if (person.getAddress() != null) {
					person.setAddress(addressDao.getById(person.getAddress()
							.getId()));
				}
				person = personDao.saveOrUpdate(person, true);
			}
		}
		
		return customer;
	}

	@Override
	public void deleteCustomer(int id) {
		logger.debug("============== delete customer");
		Customer customer = customerDao.getById(id);
		customerDao.deleteCustomer(customer);
	}

	@Override
	public void sumPoint() {
		logger.info("=====================SUM POINT");
		List<Distributor> distributors = distributorDao.getAllDistributors();
		logger.info("=====================Get <{}> distributor !",
				distributors.size());
		if (distributors != null) {
			for (Distributor distributorRoot : distributors) {
				logger.info(
						"=====================Processing distributor id <{}> !",
						distributorRoot.getId());
				if (distributorRoot.getDistributors() != null) {
					List<MonthlyPoint> monthlyPoints = monthlyPointDao
							.getAllMonthlyPointByDistributorId(distributorRoot
									.getId());
					logger.info(
							"=====================Get <{}> monthlyPoint for distributor id <{}> !",
							monthlyPoints == null ? 0:monthlyPoints.size(), distributorRoot.getId());
					
					if(monthlyPoints != null){
						for (MonthlyPoint monthlyPoint : monthlyPoints) {
							int groupPoint = 0;
							for (Distributor distributorLine1 : distributorRoot
									.getDistributors()) {
								logger.info(
										"=====================Processing line 1 distributor id <{}> !",
										distributorLine1.getId());
								if (distributorLine1.getDistributors() != null) {
									for (Distributor distributorLine2 : distributorLine1
											.getDistributors()) {
										logger.info(
												"=====================Processing line 2 distributor id <{}> !",
												distributorLine1.getId());
										groupPoint += getVolumePoint(
												distributorLine2,
												monthlyPoint.getMonth());
									}
								}
								groupPoint += getVolumePoint(distributorLine1,
										monthlyPoint.getMonth());
							}
							logger.info(
									"=====================Group point for distributor id <{}> is <{}>!",
									distributorRoot.getId(), groupPoint);
							monthlyPoint.setGroupPoint(groupPoint);
							monthlyPointDao.saveOrUpdate(monthlyPoint, true);
						}
					}
				}
			}
		}

	}

	private int getVolumePoint(Distributor dis, Month month) {
		int monthlyPoint = 0;
		if (dis.getMonthlyPoints() != null) {
			int monthly = month.getMonth();
			int yearly = month.getYear();

			for (MonthlyPoint monthlyP : dis.getMonthlyPoints()) {
				if (monthlyP.getMonth().getMonth() == monthly
						&& monthlyP.getMonth().getYear() == yearly) {
					monthlyPoint = monthlyP.getPersonalPoint();
					break;
				}
			}
		}
		logger.info(
				"=====================Volume point for distributor id <{}> is <{}>!",
				dis.getId(), monthlyPoint);
		return monthlyPoint;
	}

	@Override
	public List<MonthlyPoint> getMonthlyPointByDistributorCodeAndYear(
			String code, int year) {
		List<MonthlyPoint> result = monthlyPointDao
				.getMonthlyPointByDistributorCodeAndYear(code, year);
		return result;
	}

	@Override
	public List<Integer> getAllYearByDistributorCode(String code) {
		Distributor dis = distributorDao.getByCode(code);
		List<Integer> result = new ArrayList<Integer>();
		if (dis != null) {
			List<MonthlyPoint> monthlyPoints = monthlyPointDao
					.getAllMonthlyPointByDistributorId(dis.getId());
			for (MonthlyPoint monthlyPoint : monthlyPoints) {
				if (!result.contains(monthlyPoint.getMonth().getYear())) {
					result.add(monthlyPoint.getMonth().getYear());
				}
			}
		}
		return result;
	}

}
